FizzBuzz是一个非常简单的问题,它的那些比较简短的解答往往不可扩展,抽象也用得少——这让不同语言的解答间的比较不那么有趣。

FizzBuzzBazz是FizzBuzz的一个变体,增加了一条规则——7的倍数输出Buzz。例如,数字35将输出BuzzBazz,因为它既是5的倍数,也是7的倍数。FizzBuzzBazz加入了足够的复杂性,使得不同语言的解答值得比较。

我向你发出挑战,你可以任选语言实现FizzBuzzBazz,然后找到最短的实现。你可以直接在下面评论,也可以发twitter(带上#fizzbuzzbazz)——我会更新此页面,展示最短的程序。

当前赢家 Perl, 53字符。

FizzBuzzBazz规则如下

  • 输出1到100的数字——下列情况除外:
  • 3的倍数输出Fizz
  • 5的倍数输出Buzz
  • 7的倍数输出Bazz
  • 任何上述情况的组合,输出组合的字符串。例如,21输出FizzBazz,因为它既是3的倍数,又是7的倍数。
  • 结果列表表达式的形式是可接受的(事实上也是推荐的形式)。

Solution in LiveScript

我的解答使用LiveScript编写,64个字符(你能用你选择的语言刷新记录么?):

[1 to 100]map ->[k+\zz for k,v of{Fi:3,Bu:5,Ba:7}|it%v<1]*''||it

这不是好的编码风格——为了求短。

LiveScript是一个编译为JavaScript的语言。它和JavaScript直接对应,允许你编写表达力强的代码,不用重复编写那些样版代码。虽然LiveScript添加了很多有助于函数式编程的特性,但是在面向对象编程和面向过程编程方面也有很多改进。

现在,我来解释一下上面的代码:

  1. 首先我们输出1到100的列表。

    [1 to 100]
    
  2. 我们将对列表中的每个成员应用相同的规则,以便创建一个新列表,因此我们使用map函数。

    [1 to 100].map(...)
    
  3. map以函数为输入,这个函数会被应用到列表中的每个成员。传递给map的函数必须是单一输入和单一输出的函数。在LiveScript中,函数通过箭头定义,这个箭头从参数指向函数体。

    [1 to 100].map((x) -> ...)
    
  4. 我们使用对象表示我们的规则:

    {Fizz: 3, Buzz: 5, Bazz: 7}
    
  5. 我们需要将这组规则转换成输出的结果,由于一个数字可能是多个数字的倍数,因此它可能符合多条规则。因此,我们必须过滤对象并生成结果列表,一个空列表表示没有一条规则适用。我们可以使用列表解析来完成,使用of的话我们能得到键和值。我们打算通过将特定的规则组合应用到值(例如 3)上来输出键(例如 Fizz)。格式为[输出 for 值 of 输入 when 条件]

    [key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when ...]
    
  6. 条件很明显。如果相除余数为0,那么一个数字就是另一个数字的倍数。

    [key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0]
    
  7. 这会生成一个列表。但是我们需要的是字符串,所以我们需要join一下。

    [key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join('')
    
  8. 将这些组合起来——注意,LiveScript隐式返回函数体中的最后一个表达式(除非另有声明),所以不需要return语句。

    [1 to 100].map((x) ->
      [key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join('')
    )
    
  9. 以上代码中,如果所有规则都不符合,会输出一个空字符串,但是我们希望输出数字。由于JavaScript中,空字符串是假的,所以我们可以使用or操作符。

    [1 to 100].map((x) ->
      [key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join('') or x
    )
    

    好了,我们已经完成了解答。但是它有点长——109个字符,我们来压缩一下。

  10. 首先,大多数情况下,调用函数都不需要括号。

    [1 to 100].map (x) ->
      [key for key, value of {Fizz: 3, Buzz: 5, Bazz: 7} when x % value == 0].join('') or x
    
  11. k表示keyv表示value,这样又可以省下几个字符。

    [1 to 100].map (x) ->
      [k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} when x % v == 0].join('') or x
    
  12. 如果一个函数只有一个输入,我们可以使用it来隐式地指代它,而不用显式地声明。所以我们用it替换掉x

    [1 to 100].map ->
      [k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} when it % v == 0].join('') or it
    
  13. 如果*(乘法)操作符的右边是一个字符串的话,那么最终效果就是将左边的列表连接起来。我们用这个技巧替换掉.join('')

    [1 to 100].map ->
      [k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} when it % v == 0] * '' or it
    
  14. |when的别称。

    [1 to 100].map ->
      [k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} | it % v == 0] * '' or it
    
  15. 我们处理的是整数,也不用担心负数,所以< 1== 0等价。

    [1 to 100].map ->
      [k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} | it % v < 1] * '' or it
    
  16. 干得不错。但是还可以更进一步。在不会引起歧义的情况下,.是可选的。

    [1 to 100]map ->
      [k for k, v of {Fizz: 3, Buzz: 5, Bazz: 7} | it % v < 1] * '' or it
    
  17. zz重复出现了,我们重构下,使用\word形式表达字符串,比'word'节约一个字符:

    [1 to 100]map ->
      [k + \zz for k, v of {Fi: 3, Bu: 5, Ba: 7} | it % v < 1] * '' or it
    
  18. 最后,我们将空格移除,由于or需要前后空格,所以我们用||替换。

    [1 to 100]map ->[k+\zz for k,v of{Fi:3,Bu:5,Ba:7}|it%v<1]*''||it
    

这就是最后的解答:64个字符。

注意我们的方案扩展性相当强,因为我们使用数据表示规则,而不是使用过程式代码。

LiveScript改进 62字符

感谢Ian Barfield的解答,仍然是LiveScript,但是更短(62字符):

[++x>?[k+\zz for k,v of{Fi:3,Bu:5,Ba:7}|x%v<1]*'' for x to 99]

当前赢家 Perl 53 字符

谁能像Perl那样惜字如金?

map+(Fizz)[$_%3].(Buzz)[$_%5].(Bazz)[$_%7]||$_,1..100

来自Donnie Cameron

其他解答

Python 93 字符

Davide Griffon提供了相同思路在Python中的实现:

[''.join([w+'zz'for n,w in{3:'Fi',5:'Bu',7:'Ba'}.items()if x%n<1])or x for x in range(1,101)]

LiveScript 60字符

Pedro Rodrigues将LiveScript代码缩短到了60字符:

[\Fizz *!(++x%3)+\Buzz *!(x%5)+\Bazz *!(x%7)||x for x to 99]

但是这个解答丧失了可扩展性,如果增加规则的话,代码长度将丧失优势。


原文 The shortest FizzBuzzBazz - can you do better?
编译 SegmentFault


weakish
24.6k 声望844 粉丝

a vigorously lazy deadbeat with matured immaturity